﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

using DimsMapper.Modules;

namespace DimsMapper
{
    class ObjectsSet
    {
        int currentObjID;

        // не учитывают предметы инвентарей
        int totalObjects;
        int[] objsLevelCount = new int[3]; // не обновляется при дбовлении нового объекта (обновление только при сохранении карты)

        List<Object.GameObject>[] objectList = new List<Object.GameObject>[3];
        ProtoSet localProto;

        List<Object.GameObject> selectedObjects = new List<Object.GameObject>();

        public ObjectsSet(DBFile mapFile)
        {
            totalObjects = mapFile.ReadInt();
        }

        private int NewObjID()
        {
            currentObjID++;
            for (int i = 0; i < 3; i++)
            {
                if (objsLevelCount[i] == 0) continue;

                foreach (var obj in objectList[i])
                {
                    if (obj.id == currentObjID) {
                        currentObjID++;
                        i = -1;
                        break;
                    }
                }
            }
            if (currentObjID >= 18000 && currentObjID <= 83565) {
                currentObjID = 83565;
                NewObjID();
            }
            return currentObjID;
        }

        internal Object.GameObject PlacementObject(int pid, int tile, int rotation, int elevation)
        {
            if (rotation == -1) {
                if ((Object.Types)(pid >> 24) == Object.Types.Critter) {
                    rotation = new Random().Next(0, 6);
                } else {
                    rotation = 0;
                }
            }
            return CreateNewObject(pid, tile, rotation, elevation);
        }

        internal Object.GameObject CreateNewObject(int pid, int tile, int rotation, int elevation)
        {
            Object.GameObject obj = new Object.GameObject();
            Proto.Proto proto = ProtoFiles.GetProto(pid);

            obj.id = NewObjID();
            obj.tile = tile;
            //obj.x =
            //obj.y =
            //obj.sx =
            //obj.sy =
            //obj.frame = 0;
            obj.rotation = rotation;
            obj.fid = proto.common.FID;
            obj.flags = proto.common.Flags;
            obj.elevation = elevation;
            obj.pid = pid;
            obj.cid = -1;
            obj.lightDistance = proto.common.LightDistance;
            obj.lightIntensity = proto.common.LightIntensity;
            //obj.outline = 0;
            obj.sid = -1;
            obj.scriptIndex = -1;

            switch (obj.GetPidType())
            {
                case Object.Types.Item:
                    break;
                case Object.Types.Critter:
                    obj.data.critter.team = proto.critter.TeamNum;
                    obj.data.critter.aiPacket = proto.critter.AIPacket;

                    // derived
                    obj.data.critter.hp = proto.critter.HealthPoint;
                    obj.data.critter.ap = proto.critter.ActionPoint;
                    break;
                case Object.Types.Scenery:
                    break;
                case Object.Types.Walls:
                    break;
                case Object.Types.Misc:
                    break;
            }

            InsertObject(obj); // добавляем в массив объектов

            return obj;
        }

        private void InsertObject(Object.GameObject obj)
        {
            int index = -1;

            if (obj.tile != -1) { // для упрощения предметы добавляются в конец (при сохранении карты они должны будут помещены после своего контейнера)
                int minDiff = 40000;
                for (int i = 0; i < objectList[obj.elevation].Count; i++)
                {
                    if (objectList[obj.elevation][i].tile == -1) continue;

                    int diff = Math.Abs(objectList[obj.elevation][i].tile - obj.tile);
                    if (diff <= minDiff) {
                        minDiff = diff;
                        index = i;
                    }
                    if (diff > minDiff) break;
                }
                totalObjects++; 
            }
            if (index != -1) {
                if (objectList[obj.elevation][index].tile > obj.tile) {
                    objectList[obj.elevation].Insert(index, obj); // вставляем до, если у добавляемого объекта нех меньше 
                } else {
                    // вставляем после, если у добавляемого объекта нех больше или равен
                    while (++index < objectList[obj.elevation].Count && objectList[obj.elevation][index].tile <= obj.tile) { } // пропускаем все до следующего тайла (включая вложенные объекты)
                    objectList[obj.elevation].Insert(index, obj); 
                }
            } else {
                objectList[obj.elevation].Add(obj);
            }
        }

        private void MoveObject(Object.GameObject obj)
        {
            int fromIndex = objectList[obj.elevation].FindIndex(obj.Equals);
            objectList[obj.elevation].RemoveAt(fromIndex);

            InsertObject(obj);
        }

        Object.GameObject LoadObject(DBFile mapFile, int elevation, int mapVersion, Object.GameObject owner)
        {
            Object.GameObject obj = new Object.GameObject();
            obj.owner = owner;

            obj.id = mapFile.ReadInt();
            obj.tile = mapFile.ReadInt();
            obj.x = mapFile.ReadInt();
            obj.y = mapFile.ReadInt();
            obj.sx = mapFile.ReadInt();
            obj.sy = mapFile.ReadInt();
            obj.frame = mapFile.ReadInt();
            obj.rotation = mapFile.ReadInt();
            obj.fid = mapFile.ReadInt();
            obj.flags = mapFile.ReadInt();
            obj.elevation = mapFile.ReadInt();
            obj.pid = mapFile.ReadInt();
            obj.cid = mapFile.ReadInt();
            obj.lightDistance = mapFile.ReadInt();
            obj.lightIntensity = mapFile.ReadInt();
            int outline = mapFile.ReadInt();
            obj.sid = mapFile.ReadInt();
            obj.scriptIndex = mapFile.ReadInt();

            obj.inventory.count = mapFile.ReadInt();
            obj.inventory.capacity = mapFile.ReadInt();
            mapFile.SkipBytes(4); // obj.inventory.items = DBFile.ReadInt(mapFile);
            obj.data.update_flags = mapFile.ReadInt(); 

            int objIndex = obj.pid & 0xFFFF;
            Object.Types objType = (Object.Types)(obj.pid >> 24);

            if (!localProto.LoadProto(objType, objIndex)) {
                // прототип не найден
            }

            Proto.Proto proto;
            switch (objType)
            {
                case Object.Types.Item:
                    proto = ProtoFiles.GetProto(obj.pid);
                    switch (proto.item.ItemSubType)
                    {
                        //case Proto.Proto.ItemTypes.Armor:
                        //    break;
                        //case Proto.Proto.ItemTypes.Container:
                        //    break;
                        //case Proto.Proto.ItemTypes.Drug:
                        //    break;

                        case Proto.Proto.ItemTypes.Weapon:
                            obj.data.item.weapon.ammoQuantity = mapFile.ReadInt();
                            obj.data.item.weapon.ammoTypePid = mapFile.ReadInt();
                            break;

                        case Proto.Proto.ItemTypes.Ammo:
                            obj.data.item.ammo.quantity = mapFile.ReadInt();
                            break;

                        case Proto.Proto.ItemTypes.Misc:
                            obj.data.item.misc.charges = mapFile.ReadInt();
                            break;

                        case Proto.Proto.ItemTypes.Key:
                            obj.data.item.key.code = mapFile.ReadInt();
                            break;
                    }
                    break;
                            
                case Object.Types.Critter:
                    obj.data.critter.damageLastTurn = mapFile.ReadInt();
                    obj.data.critter.maneuver = mapFile.ReadInt();
                    obj.data.critter.ap = mapFile.ReadInt();
                    obj.data.critter.damageResults = mapFile.ReadInt();
                    obj.data.critter.aiPacket = mapFile.ReadInt();
                    obj.data.critter.team = mapFile.ReadInt();
                    obj.data.critter.whoHitMeCid = mapFile.ReadInt();

                    obj.data.critter.hp = mapFile.ReadInt();
                    obj.data.critter.radiation = mapFile.ReadInt();
                    obj.data.critter.poison = mapFile.ReadInt();
                    break;
                        
                case Object.Types.Scenery:
                    proto = ProtoFiles.GetProto(obj.pid);
                    switch (proto.scenery.Type)
                    {
                        case Proto.Proto.SceneryTypes.Door:
                            obj.data.scenery.door.openFlags = mapFile.ReadInt();
                            break;

                        case Proto.Proto.SceneryTypes.Stairs:
                            obj.data.scenery.stairs.destinationBuiltTile = mapFile.ReadInt();
                            obj.data.scenery.stairs.destinationMap = mapFile.ReadInt();
                            break;

                        case Proto.Proto.SceneryTypes.Elevator:
                            obj.data.scenery.elevator.elevatorTypeID = mapFile.ReadInt();
                            obj.data.scenery.elevator.elevatorLevel = mapFile.ReadInt();
                            break;

                        case Proto.Proto.SceneryTypes.LadderUp:
                        case Proto.Proto.SceneryTypes.LadderDown:
                            if (mapVersion == 19) { // fallout 1
                                obj.data.scenery.ladder.destinationBuiltTile = mapFile.ReadInt();
                            } else {
                                obj.data.scenery.ladder.destinationMap = mapFile.ReadInt();
                                obj.data.scenery.ladder.destinationBuiltTile = mapFile.ReadInt();
                            }
                            break;
                    }
                    break;

                case Object.Types.Misc:
                    if (obj.pid >= 0x5000010 && obj.pid <= 0x5000017) {
                        obj.data.misc.map = mapFile.ReadInt();
                        obj.data.misc.tile = mapFile.ReadInt();
                        obj.data.misc.elevation = mapFile.ReadInt();
                        obj.data.misc.rotation = mapFile.ReadInt();

                        // заменяет зеленую сетку выхода на красно-коричневую
                        //if (obj.data.misc.map <= 0 && (obj.fid & 0xFFF) < 33) {
                        //  obj.fid = art_id(OBJ_TYPE_MISC, (obj.fid & 0xFFF) + 16, FID_ANIM_TYPE(obj.fid), 0, 0);
                        //}
                    }
                    break; 
            }

            objectList[elevation].Add(obj);

            // загружаем инвентарь объекта
            if (obj.inventory.count > 0) {
                obj.inventory.items = new List<Object.ObjectInventoryItems>();

                for (int m = 0; m < obj.inventory.count; m++)
                {
                    Object.ObjectInventoryItems inventory = new Object.ObjectInventoryItems();
                    inventory.count = mapFile.ReadInt();
                    inventory.item = LoadObject(mapFile, elevation, mapVersion, obj);
                    obj.inventory.items.Add(inventory);
                }
            }
            return obj;
        }

        internal void LoadObjects(DBFile mapFile, int mapVersion, ProgressForm progress)
        {
            localProto = new ProtoSet();

            for (int elevation = 0; elevation < 3; elevation++)
            {
                objsLevelCount[elevation] = mapFile.ReadInt();
                if (objsLevelCount[elevation] > 0) objectList[elevation] = new List<Object.GameObject>();

                for (int i = 0; i < objsLevelCount[elevation]; i++)
                {
                    LoadObject(mapFile, elevation, mapVersion, null);
                    progress.ProgressStep();
                }
            }
        }

        internal int TotalObjects()
        {
            return totalObjects;
        }

        internal bool ProtoIsLocal(Object.Types type, int lstIndex)
        {
            return localProto.IsLocal(type, lstIndex);
        }

        internal void SetProtoLocal(Object.Types type, int lstIndex)
        {
            localProto.SetLocal(type, lstIndex);
        }

        internal int[] ProtoLocal(Object.Types type)
        {
            return localProto.List(type);
        }

        internal int ProtoLocalCount(Object.Types type)
        {
            return localProto.Count(type);
        }

        internal int ProtoLocalTotalCount()
        {
            return localProto.Count();
        }

        internal List<Object.GameObject> List(int elevation)
        {
            return objectList[elevation - 1];
        }

        internal void ClearSelectedObject()
        {
            foreach (var sObj in selectedObjects)
            {
                sObj.selected = false;
            }
            selectedObjects.Clear();
        }

        internal void AddSelectObject(Object.GameObject obj)
        {
            obj.selected = true;
            selectedObjects.Add(obj);
        }

        internal void SelectObject(Object.GameObject obj)
        {
            ClearSelectedObject();
            AddSelectObject(obj);
        }

        internal void RemoveSelectObject(Object.GameObject obj)
        {
            obj.selected = false;
            selectedObjects.Remove(obj);
        }

        internal int SelectedCount()
        {
            return selectedObjects.Count;
        }

        internal Object.GameObject GetSelectObject()
        {
            return (selectedObjects.Count != 0) ? selectedObjects.Last() : null;
        }

        internal bool ObjectsIsSelected()
        {
            return (selectedObjects.Count != 0);
        }

        internal void MoveSelectedObjects(int deltaX, int deltaY)
        {
            int hexX, hexY;
            foreach (var obj in selectedObjects)
            {
                obj.GetHexXY(out hexX, out hexY);
                hexX += deltaX;
                hexY += deltaY;

                if (hexX < 0 || hexX >= 200 || hexY < 0 || hexY >= 200) continue;

                obj.SetHex(hexX, hexY);

                MoveObject(obj);
            }
        }

        internal bool DeleteSelectedObjects()
        {
            if (!ObjectsIsSelected()) return false;

            foreach (var obj in selectedObjects)
            {
                if (obj.inventory.count > 0) {
                    foreach (var el in obj.inventory.items)
                    {
                        objectList[obj.elevation].Remove(el.item);
                    }
                }
                objectList[obj.elevation].Remove(obj);
                totalObjects--;
            }
            selectedObjects.Clear();

            return true;
        }

        internal void AddInventoryItem(Object.GameObject sourceObj, int pid)
        {
            if (sourceObj.inventory.items == null) {
                sourceObj.inventory.items = new List<Object.ObjectInventoryItems>();
                sourceObj.inventory.capacity = 10;
            }

            // добавление к уже имеющимся предметам
            foreach (var inven in sourceObj.inventory.items)
            {
                if (inven.item.pid == pid && inven.item.GetSlot() == Object.Slot.None) {
                    if (inven.item.inventory.count == 0) {
                        inven.count++; // увеличили счетчик
                        return;
                    }
                }
            }

            Object.GameObject item = CreateNewObject(pid, -1, 0, sourceObj.elevation);
            item.owner = sourceObj;

            sourceObj.inventory.items.Add(new Object.ObjectInventoryItems(item));
            sourceObj.inventory.count++;
        }

        internal void RemoveInventoryItem(Object.GameObject source, int itemIndex)
        {
            Object.GameObject item = source.inventory.items[itemIndex].item;
            
            // проверка вложенного инвентаря (контейнеры)
            if (item.inventory.count > 0) {
                for (int i = item.inventory.count - 1; i >= 0; i--)
                {
                    RemoveInventoryItem(item, i); // удаляет под предмет
                }
            }
            // удалить основной предмет
            source.inventory.items.RemoveAt(itemIndex);
            source.inventory.count--;
        }
    }
}
